package com.fxnotes.book.presenter;

import com.fxnotes.Resources;
import com.fxnotes.command.ShowBooks;
import com.fxnotes.event.RemoveChapterEvent;
import com.fxnotes.event.RemovePageEvent;
import com.fxnotes.event.UpdateChapterEvent;
import com.fxnotes.event.UpdatePageEvent;
import com.fxnotes.book.node.BookDetailsNode;
import com.fxnotes.command.ShowPage;
import com.fxnotes.common.Utils;
import com.fxnotes.common.thread.Executable;
import com.fxnotes.common.thread.TaskExecutor;
import com.fxnotes.common.ui.NodePresenter;
import com.google.common.eventbus.EventBus;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.fxnotes.domain.model.Book;
import com.fxnotes.domain.model.Chapter;
import com.fxnotes.domain.model.Page;
import com.fxnotes.domain.repository.IBookRepository;
import com.fxnotes.domain.repository.IChapterRepository;
import com.fxnotes.domain.repository.IPageRepository;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.scene.Parent;

@Singleton
public class BookDetailsPresenter extends NodePresenter<Parent> {

    private final EventBus eventBus;
    private final Provider<TaskExecutor> tasksProvider;
    private IBookRepository bookRepository;
    private IChapterRepository chapterRepository;
    private IPageRepository pageRepository;
    private BookDetailsNode bookDetailsNode;
    private Book book;
    private static final Logger logger = Logger.getLogger(BookDetailsPresenter.class.getName());

    @Inject
    public BookDetailsPresenter(EventBus eventBus, Provider<TaskExecutor> tasksProvider) {
        super(Resources.PRESENTER_BOOK_DETAILS);
        this.eventBus = eventBus;
        this.tasksProvider = tasksProvider;
    }

    @Inject
    public void setRepositories(IBookRepository bookRepository, IChapterRepository chapterRepository, IPageRepository pageRepository) {
        this.bookRepository = bookRepository;
        this.chapterRepository = chapterRepository;
        this.pageRepository = pageRepository;
    }

    @Inject
    public void setNode(BookDetailsNode node) {
        this.bookDetailsNode = node;
    }

    // API
    @Override
    public Parent getNode() {
        return bookDetailsNode.getNode();
    }

    public void loadBookDetails(final Book book) {
        this.book = book;
        reloadBookDetails();
    }

    public void showBooks() {
        eventBus.post(new ShowBooks());
    }

    public void showPage(Chapter chapter, Page page) {
        eventBus.post(new ShowPage(book, chapter, page));
    }
    
    // chapters
    public void addChapter(final String name) {
        if ("".equals(name)) {
            reportError("Empty chapter name!");
            return;
        }

        executeTask(new BookDetailsExeccutable() {

            @Override
            public void execute() throws Exception {
                Chapter chapter = new Chapter(name);
                logger.log(Level.INFO, "Adding chapter {0}...", chapter);
                chapterRepository.add(book, chapter);
            }

            @Override
            public void onSuccess() {
                reloadChapters();
            }
        });
    }

    public void updateChapter(final Chapter chapter, final String name) {
        if ("".equals(name)) {
            reportError("Empty chapter name!");
            return;
        }

        if (Utils.containsEntityWithName(book.getChapters(), name)) {
            reportError("Chapter already exists!");
            return;
        }

        executeTask(new BookDetailsExeccutable() {

            @Override
            public void execute() throws Exception {
                logger.log(Level.INFO, "Updating chapter {0} with name {1}...", new Object[]{chapter, name});
                chapterRepository.updateName(chapter, name);
            }

            @Override
            public void onSuccess() {
                eventBus.post(new UpdateChapterEvent(chapter));
                reloadChapters();
            }
        });
    }

    public void removeChapter(final Chapter chapter) {
        executeTask(new BookDetailsExeccutable() {

            @Override
            public void execute() throws Exception {
                logger.log(Level.INFO, "Removing chapter {0}...", chapter);
                chapterRepository.remove(book, chapter);
            }

            @Override
            public void onSuccess() {
                eventBus.post(new RemoveChapterEvent(chapter));
                reloadChapters();
                clearPages();
            }
        });
    }

    public void updateChaptersPosition(final List<Chapter> chapters, final Chapter movedChapter) {
        executeTask(new BookDetailsExeccutable() {

            @Override
            public void execute() throws Exception {
                logger.log(Level.INFO, "Update chapters, chapter {0} changed position", movedChapter);
                bookRepository.updateChapters(book, chapters);
            }

            @Override
            public void onSuccess() {
                reloadChapters();
            }
        });
    }

    // page
    public void addPage(final Chapter chapter, final String name) {
        executeTask(new BookDetailsExeccutable() {

            @Override
            public void execute() throws Exception {
                Page page = new Page(name);
                logger.log(Level.INFO, "Adding page {0}...", page);
                pageRepository.add(chapter, page);
            }

            @Override
            public void onSuccess() {
                reloadPages(chapter);
            }
        });
    }

    public void updatePage(final Chapter chapter, final Page page, final String name) {
        if ("".equals(name)) {
            reportError("Empty page name!");
            return;
        }

        if (Utils.containsEntityWithName(chapter.getPages(), name)) {
            reportError("Page already exists!");
            return;
        }

        executeTask(new BookDetailsExeccutable() {

            @Override
            public void execute() throws Exception {
                logger.log(Level.INFO, "Updating page {0} with name {1}...", new Object[]{page, name});
                pageRepository.updateName(page, name);
            }

            @Override
            public void onSuccess() {
                eventBus.post(new UpdatePageEvent(page));
                reloadPages(chapter);
            }
        });
    }

    public void removePage(final Chapter chapter, final Page page) {
        executeTask(new BookDetailsExeccutable() {

            @Override
            public void execute() throws Exception {
                logger.log(Level.INFO, "Removing page {0}...", page);
                pageRepository.remove(chapter, page);
            }

            @Override
            public void onSuccess() {
                eventBus.post(new RemovePageEvent(page));
                reloadPages(chapter);
            }
        });
    }

    public void updatePagesPosition(final Chapter chapter, final List<Page> pages, final Page movedPage) {
        executeTask(new BookDetailsExeccutable() {

            @Override
            public void execute() throws Exception {
                logger.log(Level.INFO, "Update pages, page {0} changed position", movedPage);
                chapterRepository.updatePages(chapter, pages);
            }

            @Override
            public void onSuccess() {
                reloadPages(chapter);
            }
        });
    }
    // helper methods & classes

    private void executeTask(Executable executable) {
        tasksProvider.get().execute(executable);
    }

    abstract class BookDetailsExeccutable implements Executable {

        @Override
        public void onFail(String message) {
            reportError(message);
        }
    }

    private void reloadBookDetails() {
        bookDetailsNode.reloadBook(book);
    }

    private void reloadChapters() {
        bookDetailsNode.reloadChapters(book);
    }

    private void reloadPages(Chapter chapter) {
        bookDetailsNode.reloadPages(chapter, true);
    }

    private void clearPages() {
        bookDetailsNode.clearPages();
    }

    private void reportError(String message) {
        bookDetailsNode.reportError(message);
        logger.severe(message);
    }
}
